package scanner;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import regexInput.Node;

import ll1.LL1Node;

public class MiniReScan {
	
	public DSList struct;
	private int section;
	private char[] negateString;
	private scanner.Node expres, regexp;
	private String text, tempToken, globalToken;
	private int textIndex;
	private int parenScope;	//something is wrong if this is not 0 at the end
	private boolean inBracket = false;
	private boolean nestedBrackets = false;
	private boolean matchError = false;
	private ArrayList<String> tokenList;
	private String startToken;
	private int errorAt;
	private int lineNo;
	private String errLog;
	
	//For LL1 Parser
	private ArrayList<LL1Node> ruleList;
	
	public MiniReScan(){
		//for initalization
	}
	
	public MiniReScan(String path) throws IOException{
		errLog = "";
		startToken = "";
		section = 0;
		tokenList = new ArrayList<String>();
		struct = new DSList();
		BufferedReader in = new BufferedReader(new FileReader(path));
		ruleList = new ArrayList<LL1Node>();
		lineNo = 1;
		errorAt = -1;
		while (in.ready()) { 
			  textIndex = 0;
			  text = in.readLine();
			  Split();
			  lineNo++;
			}
		if (matchError) {
			errLog += ("Something was not parsed as expected. Check input file for errors. Output may be incorrect.");
		}
		if (parenScope != 0) {
			errLog += ("Number of '(' did not match the number of ')'. Check input file for unmatched parentheses.");
		}
		if (nestedBrackets) {
			errLog += ("Nested square brackets are not allowed. Please check input file for nested brackets.");
		}
		if (inBracket) {
			errLog += ("An unclosed square bracket was found. Pleace check input file for unclosed brackets");
		}
		if (errorAt != -1){
			errLog += ("The first error occured at line " + lineNo);
		}
	}
	/**
	 * Refer to Appendix 1: Formal Grammar for REGEX
	 * I tried to keep the naming conventions the same as the ones
	 * in the grammar to make it easier to read and refer back and forth.
	 */
	//Naming scheme matching formal grammar
	//Denote as rexp in formal grammar
	public void Split(){
		boolean gotName, gotSpecs;
		
		if(section == 0){	// tokens
			String currentWord = "";
			while (peekToken() != (char)0) {
				if (peekToken() == ' ') {
					matchToken();
					if (!currentWord.equals("")) {
						tokenList.add(currentWord);
						currentWord = "";
					}
				} else {
					currentWord += matchToken();
				}
			}
			if (currentWord != "") {
				tokenList.add(currentWord);
				currentWord = "";
			}
			if (!tokenList.contains("%%")) {
				section++;
			} else {
				tokenList.clear();
			}
		}
		else if (section == 1){ //start
			if (text.length() > 1 && text.charAt(0) != '\n' && text.charAt(0) != '%') {
				while(skipable(peekToken())){
					matchToken();
				}
				while(!skipable(peekToken())){
					if (text.length() == textIndex) {
						break;
					}
					if (peekToken() != (char)0) {
						startToken += matchToken();
					}
				}
				section++;
			}
		} else {
			LL1Node node = toLL1Node(text);
			if (node != null) {
				ruleList.add(node);
			}
		}
	}
	
	//AKA rexp'
	private void rexp(){
		if(peekToken() == '|')
		{
			matchToken();
			while(peekToken() == ' '){
				skipToken();
			}
			rexp1();
			while(peekToken() == ' '){
				skipToken();
			}
			rexp();
			while(peekToken() == ' '){
				skipToken();
			}
		}
		else{
			//matchError = true;//should have started with |
			return;
		}
	}
	
	private void rexp1(){
		rexp2();
		rexp1p();
	}
	
	//rexp1'
	private void rexp1p(){
		if(aReChar(peekToken())){
			rexp2();
			rexp1p();	//From formal grammar rexp1p = rexp1'
		}
		else{
			return;
		}
	}
	
	private void rexp2(){
		if(peekToken() == '('){
			matchToken();
			while(peekToken() == ' '){
				skipToken();
			}
			while(peekToken() != ')'){
			rexp1();
			while(peekToken() == ' '){
				skipToken();
			}
			rexp();
			while(peekToken() == ' '){
				skipToken();
			}
			}
			matchToken(); 	//Getting ')' char
			rexp2_tail();
		}
		else if(aReChar(peekToken())){
			while(aReChar(peekToken())){
				matchToken();
			}
			rexp2_tail();
		}
		else{
			rexp3();
		}
	}
	
	private void rexp2_tail(){
		if(peekToken() == '*' || peekToken() == '+'){
			matchToken();
		}
		else{
			return;
		}
	}
	
	private void rexp3(){
		if(peekToken() == '$' || peekToken() == '.' || peekToken() == '['){
			char_class();
		}
		else{
			return;
		}
	}
	
	private void char_class(){
		if(peekToken() == '.'){ //Change to any char
			matchToken();
		}
		else if(peekToken() == '['){
			skipToken();	// '[' char
			char_class1();
		}
		else if(peekToken() == '$'){
			defineClass();
		}
		else {
			if (errorAt == -1) {
				errorAt = lineNo;
			}
			matchError = true;//it needed to be . [ or $
		}
	}
	
	private void char_class1(){
		if(aClsChar(peekToken())){
			char_set();
		}
		else{
			exclude_set();
		}
	}
/**	
	private void char_set(){	
		if(text.charAt(textIndex+1) == '-'){	//No change, my solution works the same way
			range();
		}
		else{
			while(aClsChar(peekToken())){
				matchToken();
			}
			if (peekToken() != ']') {
				matchError = true; //something went wrong if it isn't ]
			}
			skipToken(); 	//Assume ']'
		}
	}
	*/
	private void char_set(){	
			while(aClsChar(peekToken()) || peekToken() == '-'){
				if(peekToken() == '-'){
					range();
				}
				else{
					matchToken();
				}
			}
			if (peekToken() != ']') {
				if (errorAt == -1) {
					errorAt = lineNo;
				}
				matchError = true; //something went wrong if it isn't ]
			}
			skipToken(); 	//Assume ']'
	}
	
	/**
	private void range(){
		int start = 0, end = 0;
		if(aClsChar(peekToken())){
			start = matchToken();
		}
		if (peekToken() != '-') {
			matchError = true; //something went wrong if it isn't ]
		}
		skipToken(); 	//Assume '-' char
		
		end = peekToken();
		
		//getRange
		for(int count = start + 1; count < end; count++){
			tempToken += (char) count;
		}
		
		if(aClsChar(peekToken())){
			matchToken();		//Getting the last char
		}
		if(aClsChar(peekToken())){
			char_set();
		}
		else{
			if (peekToken() != ']') {
				matchError = true; //something went wrong if it isn't ]
			}
			skipToken();	//Assume ']' char
		}
	}
	*/
	
	private void range(){
		int start = text.charAt(textIndex - 1), end = 0;
		if (peekToken() != '-') {
			if (errorAt == -1) {
				errorAt = lineNo;
			}
			matchError = true; //something went wrong if it isn't -
		}
		skipToken(); 	//Assume '-' char
		
		end = peekToken();
		if (start >= end) {
			if (errorAt == -1) {
				errorAt = lineNo;
			}
			matchError = true; //[a-z] is okay, [z-a] or [a-a] isn't. 
		}
		//getRange
		for(int count = start + 1; count < end; count++){
			tempToken += (char) count;
		}
		
		if(aClsChar(peekToken())){
			matchToken();		//Getting the last char
		}
	}
	
	private void exclude_set(){
		if (peekToken() != '^') {
			if (errorAt == -1) {
				errorAt = lineNo;
			}
			matchError = true; //something went wrong if it isn't ^
		}
		skipToken();	//Assume ^ char
		char_set();
		while(skipable(peekToken())){
			skipToken();
		}
		if (peekToken() != 'I') {
			if (errorAt == -1) {
				errorAt = lineNo;
			}
			matchError = true; //something went wrong if it isn't I
		}
		skipToken();	//Assume I
		if (peekToken() != 'N') {
			if (errorAt == -1) {
				errorAt = lineNo;
			}
			matchError = true; //something went wrong if it isn't N
		}
		skipToken();	//Assume N
		while(skipable(peekToken())){
			skipToken();
		}
		exclude_set_tail();
	}
	
	private void exclude_set_tail(){
		if(peekToken() == '['){
			skipToken();	//The [ char
			char_set();
		}
		else{
			//Find the char class and use that spec list
			boolean found = false;
			int tempIndex = textIndex;
			char[] excludeList, specList; 
			String finalList = "";
			excludeList = tempToken.toCharArray();
			String contents = tempToken, temp;
			tempToken = "";
			defineClass();
			textIndex = tempIndex - 1;	//Overcount, need the space as delimiter
			temp = tempToken;
			tempToken = contents;
			specList = struct.getSpecs(temp);
			for(int i = 0; i < specList.length; i++){
				for(int j = 0; j < excludeList.length; j++){
					if(specList[i] == excludeList[j]){
						found = true;
						j = excludeList.length;
					}
				}
				if(!found){
					finalList += specList[i];
				}
				else{
					found = false;
				}
			}
			tempToken = finalList;
		}
	}
	
	private void defineClass(){
		while(aReChar(peekToken())){
			matchToken();
		}
	}
	
	private boolean aReChar(char a){
		if(peekToken() == ' ' ||
			peekToken() == '*' ||
			peekToken() == '+' ||
			peekToken() == '?' ||
			peekToken() == '|' ||
			peekToken() == '[' ||
			peekToken() == ']' ||
			peekToken() == '(' ||
			peekToken() == ')' ||
			peekToken() == '.' ||
			peekToken() == 39 ||	//numerical value for '
			peekToken() == '"'){
			return false;
		}
		if(peekToken() < 32){		//Not within ASCII Printable
			return false;
		}
		//checking for escaped char
		if(peekToken() == 92){ 	//numerical value for \
			skipToken();
			if(peekToken() == ' ' ||
					peekToken() == 92 ||
					peekToken() == '*' ||
					peekToken() == '+' ||
					peekToken() == '?' ||
					peekToken() == '|' ||
					peekToken() == '[' ||
					peekToken() == ']' ||
					peekToken() == '(' ||
					peekToken() == ')' ||
					peekToken() == '.' ||
					peekToken() == 39 ||	//numerical value for '
					peekToken() == '"'){
					
				textIndex--;	
				return true;
				}
			else{
				textIndex--;
				return false;
			}
		}
		else{
			return true;
		}
	}
	private boolean aClsChar(char a){
		if(peekToken() == '^' ||
				peekToken() == '-' ||
				peekToken() == '[' ||
				peekToken() == ']'){
				return false;
			}
		if(peekToken() < 32){		//Not within ASCII Printable
			return false;
		}
		//checking for escaped char
				if(peekToken() == 92){ 	//numerical value for \
					skipToken();
					if(peekToken() == 92 ||
							peekToken() == '^' ||
							peekToken() == '-' ||
							peekToken() == '[' ||
							peekToken() == ']'){
							
						textIndex--;	
						return true;
						}
					else{
						textIndex--;
						return false;
					}
				}
		else{
			return true;
		}
	}
	
	private boolean skipable(char a){
		if(peekToken() == ' ' ||
				peekToken() == '\t' ||
				peekToken() == '\n' ||
				peekToken() == '\r'){
				return true;
			}
			else{
				return false;
			}
	}	
	
	private char matchToken(){
		if(textIndex == text.length()){
			textIndex++;
			return (char) 0;
		}
		else{
			char thing;
			//get escaped characters here
			if(peekToken() == 92){
				skipToken();
			}
			if(peekToken() == 92){
				skipToken();
			}
			thing = text.charAt(textIndex);
			if (thing == '(') {
				parenScope++;
			}
			if (thing == ')') {
				parenScope--;
			}
			if (thing == '[') {
				if (inBracket) {
					if (errorAt == -1) {
						errorAt = lineNo;
					}
					nestedBrackets = true;
				}
				inBracket = true;
			}
			if (thing == ']') {
				if (!inBracket) {
					matchError = true;
				}
				inBracket = false;
			}
			textIndex++;
			tempToken += thing;
			return thing;
		}
	}
	
	private char peekToken(){
		if(textIndex >= text.length()){
			return (char) 0;
		}
		else{
			char thing;
			thing = text.charAt(textIndex);
			return thing;
		}
	}
	
	private void skipToken(){
		if (textIndex + 1 < text.length()) {
			textIndex++;
		}
	}
	
	/**
	 * For LL1 Parser
	 */
	/**
	 * Takes in a line from the rules/grammar and converts it to a LL1 Node
	 * @param line
	 * @return a LL1Node that the parser can read
	 */
	public LL1Node toLL1Node(String line){
		/*Here we check for rules*/
		boolean gotName = false;
		boolean gotSpecs = false;
		LL1Node node = new LL1Node();
		
		while(skipable(peekToken())){
			matchToken();
		}
		tempToken = "";
		
		//Gets the Character Class Name
		rexp1();
		rexp();
		node.setName(tempToken);
		
		while(skipable(peekToken())){
			matchToken();
		}
		if (textIndex < text.length()-2) {
			if (peekToken() != '-' && !node.getName().equals("%%")) {
				if (errorAt == -1) {
					errorAt = lineNo;
				}
				matchError = true; //something went wrong if it isn't - 
			}
			matchToken();
			if (peekToken() != '>' && !node.getName().equals("%%")) {
				matchError = true; //something went wrong if it isn't >
			}
			matchToken();
			while(skipable(peekToken())){
				matchToken();
			}
			tempToken = "";
		}
		tempToken = "";
		//Takes the Specifications of Character Class
		while(textIndex < text.length()){
			if (skipable(peekToken())) {
				if (!tempToken.equals("")) {
					node.getContent().add(new ll1.Token(tempToken, isTerminal(tempToken)));
				}
				matchToken();
				tempToken = "";
			} else {
				matchToken();
			}
		}
		if (!tempToken.equals("")) {
			node.getContent().add(new ll1.Token(tempToken, isTerminal(tempToken)));
		}
		if (!node.getName().equals("") && !node.getName().equals("%%") && !node.getContent().isEmpty()){
			return node;
		} else {
			return null;
		}
	}
	public ArrayList<String> getTokenList() {
		return tokenList;
	}
	public ArrayList<LL1Node> getRules() {
		return ruleList;
	}
	public String getStartToken() {
		return startToken;
	}
	
	private boolean isTerminal(String token) {
		return (tokenList.contains(token));
	}
	
	public String toString(){
		return struct.toString();
	}
	
	public String getErrorLog(){
		return errLog;
	}
}
